#pragma once
// -------------------------------------------------
// ZED Kit
// -------------------------------------------------
//   File Name: memory.hpp
//      Author: Ziming Li
//     Created: 2021-02-05
// -------------------------------------------------
// Copyright (C) 2021 MingYang Software Technology.
// -------------------------------------------------

#ifndef ZED_MEMORY_HPP
#define ZED_MEMORY_HPP

#include <cstring>
#include <memory>
#include <type_traits>
#include <utility>
#include "./assert.h"

namespace zed {

/**
 * unique_resource
 */

template <typename T>
struct is_resource
{
    static constexpr bool value = std::is_integral<T>::value || std::is_pointer<T>::value;
};

template <typename T>
struct default_resource_finalizer
{
    void operator()(T resource) const;
};

template <typename T>
struct default_resource_traits
{
    static constexpr T invalid_value = 0;
};

template <typename T, typename Finalizer = default_resource_finalizer<T>, typename Traits = default_resource_traits<T>, typename = std::enable_if<is_resource<T>::value>>
class unique_resource
{
public:
    unique_resource(void) : m_resource(std::make_pair(Traits::invalid_value, Finalizer())) {}
    explicit unique_resource(T resource) : m_resource(std::make_pair(resource, Finalizer())) {}
    unique_resource(unique_resource &&r) : m_resource(std::make_pair(r.release(), Finalizer())) {}

    ~unique_resource(void)
    {
        if (is_valid(m_resource.first))
            finalize(m_resource.first);
    }

    explicit operator bool() const { return is_valid(m_resource.first); }

    T get(void) const { return m_resource.first; }
    T release(void) { return std::exchange(m_resource.first, Traits::invalid_value); }
    void swap(unique_resource &r) { std::swap(m_resource.first, r.m_resource.first); }

    void reset(T resource = Traits::invalid_value)
    {
        T old_resource = std::exchange(m_resource.first, resource);
        if (is_valid(old_resource))
            finalize(old_resource);
    }

    unique_resource(const unique_resource &) = delete;
    unique_resource& operator=(const unique_resource &) = delete;
protected:
    static bool is_valid(const T resource) { return Traits::invalid_value != resource; }
private:
    void finalize(T resource) { m_resource.second(resource); }

    std::pair<T, Finalizer> m_resource;
};

/**
 * Pointer Wrappers
 */

template <typename T>
class ptr_base
{
public:
    T* get(void) const { return m_ptr; }

    T& operator*() const { return *m_ptr; }
    T* operator->() const { return m_ptr; }
    operator bool() const { return nullptr != m_ptr; }
    bool operator!() const { return nullptr == m_ptr; }

    bool operator==(const ptr_base& o) const { return m_ptr == o.m_ptr; }
    template <typename U>
    bool operator==(U* ptr) const { return m_ptr == ptr; }
    template <typename U>
    bool operator!=(const U& o) const { return !(*this == o); }
protected:
    explicit ptr_base(T* ptr) : m_ptr(ptr) {}

    T* m_ptr;
};

template <typename T>
std::unique_ptr<T> wrap_unique(T *p) { return std::unique_ptr<T>(p); }

template <typename T>
std::shared_ptr<T> wrap_shared(T *p) { return std::shared_ptr<T>(p); }

/**
 * scoped_ptr
 */

template <typename T>
class scoped_ptr : public ptr_base<T>
{
public:
    explicit scoped_ptr(T *ptr = nullptr) : ptr_base<T>(ptr) {}
    constexpr scoped_ptr(std::nullptr_t) : ptr_base<T>(nullptr) {}
    scoped_ptr(scoped_ptr &&o);
    ~scoped_ptr(void);

    scoped_ptr& operator=(scoped_ptr&&);
    scoped_ptr& operator=(std::nullptr_t);

    void reset(T *ptr = nullptr);
    T* release(void);
private:
    scoped_ptr(const scoped_ptr &o) = delete;
    scoped_ptr& operator=(const scoped_ptr &o) = delete;
};

template <typename T>
zed::scoped_ptr<T> wrap_scoped(T *p) { return zed::scoped_ptr<T>(p); }

/**
 * weak_ptr & enable_weak_from_this
 */

template <typename T> class enable_weak_from_this;

template <typename T>
class weak_ptr
{
    friend class enable_weak_from_this<T>;
public:
    weak_ptr(void) = default;
    constexpr weak_ptr(std::nullptr_t) { }

    bool expired(void) const { return !m_slot || nullptr == *m_slot; }
    void clear(void) { m_slot.reset(); }

    T& operator*() const { return *get(); }
    T* operator->() const { return get(); }
private:
    weak_ptr(const std::shared_ptr<T *> &slot) : m_slot(slot) {}

    T* get(void) const;

    std::shared_ptr<T *> m_slot;
};

template <typename T>
class enable_weak_from_this
{
public:
    ~enable_weak_from_this(void);

    weak_ptr<T> weak_from_this(void);
protected:
    enable_weak_from_this(void);
private:
    const std::shared_ptr<T *> m_slot;
};

/**
 * Type Casting
 */

template <typename DstType, typename SrcType>
inline DstType bit_cast(const SrcType &src)
{
    static_assert(sizeof(DstType) == sizeof(SrcType));

    DstType dst;
    memcpy(&dst, &src, sizeof(DstType));
    return dst;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementations

template <typename T>
scoped_ptr<T>::scoped_ptr(scoped_ptr &&o) : ptr_base<T>(o.m_ptr)
{
    o.m_ptr = nullptr;
}

template <typename T>
scoped_ptr<T>::~scoped_ptr(void)
{
    if (nullptr != this->m_ptr)
        delete this->m_ptr;
}

template <typename T>
scoped_ptr<T>& scoped_ptr<T>::operator=(scoped_ptr<T>&& o)
{
    reset(o.m_ptr);
    o.m_ptr = nullptr;
    return *this;
}

template <typename T>
scoped_ptr<T>& scoped_ptr<T>::operator=(std::nullptr_t)
{
    reset(nullptr);
    return *this;
}

template <typename T>
void scoped_ptr<T>::reset(T *ptr)
{
    if (this->m_ptr == ptr)
        return;

    if (nullptr != this->m_ptr)
        delete this->m_ptr;
    this->m_ptr = ptr;
}

template <typename T>
T* scoped_ptr<T>::release(void)
{
    T* ret = this->m_ptr;
    this->m_ptr = nullptr;
    return ret;
}

template <typename T>
class scoped_ptr<T[]> : public ptr_base<T>
{
public:
    explicit scoped_ptr(T *ptr = nullptr) : ptr_base<T>(ptr) {}
    scoped_ptr(scoped_ptr &&o) : ptr_base<T>(o.m_ptr)
    {
        o.m_ptr = nullptr;
    }
    ~scoped_ptr(void)
    {
        if (nullptr != this->m_ptr)
            delete[] this->m_ptr;
    }

    void reset(T *ptr = nullptr)
    {
        if (this->m_ptr == ptr)
            return;

        if (nullptr != this->m_ptr)
            delete[] this->m_ptr;
        this->m_ptr = ptr;
    }
private:
    scoped_ptr(const scoped_ptr &o) = delete;
    scoped_ptr& operator=(const scoped_ptr &o) = delete;
};

template <typename T>
enable_weak_from_this<T>::enable_weak_from_this(void) : m_slot(std::make_shared<T *>(static_cast<T *>(this)))
{
}

template <typename T>
enable_weak_from_this<T>::~enable_weak_from_this(void)
{
    *m_slot = nullptr;
}

template <typename T>
weak_ptr<T> enable_weak_from_this<T>::weak_from_this(void)
{
    return weak_ptr<T>(m_slot);
}

template <typename T>
T* weak_ptr<T>::get(void) const
{
    ZASSERT(m_slot);
    ZASSERT(nullptr != *m_slot);
    return *m_slot;
}

} // namespace zed

#endif // ZED_MEMORY_HPP
